package pikasial.fs;

import java.io.File;
import java.util.ArrayList;
import java.util.Observable;

import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileSystemView;
import javax.swing.tree.TreePath;

/**
 * Représentation du système de fichiers de l'application.
 * <p>
 * Le système de fichier de l'application contient tous les fichiers
 * et dossiers ajoutés à l'application après avoir été importés
 * par l'utilisateur.
 * Le système de fichiers est représenté sous forme arborescente
 * (instance de <code>DefaultTreeModel</code>)
 * La classe est observée par <code>NavigateurDossier</code>.
 * L'arbre ne présente que les dossiers, les fichiers qu'ils contiennent étant
 * affichés lors d'une sélection.
 *
 * @author ESIAL - Groupe 14
 */
@SuppressWarnings("serial")
public final class PikasialFS extends Observable {

    /** Modèle de la représentation arborescente */
    private PikasialTreeModel ptm;

    public static final PikasialFS INSTANCE = new PikasialFS();

    /**
     * Crée une instance de PikasialFS
     * <p>
     * Crée l'arbre, à partir de sa racine, et ré-importe les dossiers
     * déjà importés par l'utilisateur.
     * (système de sauvegarde à revoir)
     */
    private PikasialFS() {
        ptm = new PikasialTreeModel(new Racine());
    }

    public static PikasialFS getInstance() {
        return INSTANCE;
    }

    /**
     * Ajoute un dossier au système de fichiers.
     */
    public void add(final Dossier d, final boolean isRecursiveCall) {
        new Thread(new Runnable() {
            public void run() {
                d.scanFolderOnDisk();
                if (d.containsImage() || d.hasSousDossiers()) {
                    final TreePath tp = placerDossierDansArbre(d, true);
                    java.util.SortedSet<Dossier> ssDossiers
                        = d.getSousDossiers();
                    /* A quoi sert ton synchronized? Un seul thread peut accéder à ce bout de code non?*/
                    synchronized(ssDossiers) {
                        java.util.Iterator<Dossier> it = ssDossiers.iterator();
                        while (it.hasNext()) {
                            add(it.next(), true);
                        }
                    }
                    if (!isRecursiveCall) {
                        SwingUtilities.invokeLater(new Runnable() {
                            public void run() {
                                ptm.fireTreeStructureChanged(ptm.getRoot());
                                notifyObservers(tp);
                                pikasial.images.ListeAlbums.getInstance().setAlbumAffiche(d);
                            }
                        });
                    }
                    pikasial.images.ListeAlbums.getInstance().ajouterDossier(d);
                }
            }
        }).start();
    }

    /**
     * Ajoute un fichier au système de fichiers de Pikasial.
     * <p>
     * Cette méthode est appelée lorsque l'utilisateur importe
     * explicitement un fichier dans Pikasial.
     * Elle place le dossier parent dans l'arbre.
     *
     * @param f - le fichier à ajouter
     * @param seSouvenir - indique si l'application doit se souvenir
     * du fichier importé (pour le réimporter plus tard)
     *
     * @see #placerDossierDansArbre(Dossier)
     * @see #alreadyInModel(DefaultMutableTreeNode, FSElement)
     */
    public void add(final Fichier f, final boolean afficher) {
        if (f.containsImage()) {
            final TreePath tp = placerDossierDansArbre(
                new Dossier(f.getJavaFile().getParentFile()), false);
            final Dossier dParent = (Dossier) tp.getLastPathComponent();
            dParent.addImage(new pikasial.images.PikImage(f.getJavaFile()));

            SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        ptm.fireTreeStructureChanged(ptm.getRoot());
                        if (afficher) {
                            notifyObservers(tp);
                            pikasial.images.ListeAlbums.getInstance().setAlbumAffiche(dParent);
                        }
                 }
            });
            pikasial.images.ListeAlbums.getInstance().ajouterDossier(dParent);
        }
    }

    /**
     * Retourne le modèle de l'arbre.
     * @return le modèle de l'arbre
     */
    public PikasialTreeModel getModel() {
        return ptm;
    }

    /**
     * Place le dossier spécifié dans le modèle.
     * <p>
     * Crée le chemin du dossier depuis la racine, et ajoute les différents
     * noeuds un par un s'ils n'existent pas déjà.
     *
     * @param d - le dossier à placer
     * @return le noeud correspondant au dossier dans l'arbre.
     */
    public TreePath placerDossierDansArbre(Dossier d, boolean importDossier) {
        FileSystemView fsv = FileSystemView.getFileSystemView();
        File defaultDir = fsv.getDefaultDirectory();

    	ArrayList<Dossier> fsPath = new ArrayList<Dossier>();
        fsPath.add(0,d);
        int i = 1;
        File f = d.getJavaFile();
        while ((!f.equals(defaultDir)) && (f = f.getParentFile()) != null) {
            fsPath.add(i,new Dossier(f));
            i++;
        }

        ArrayList<Dossier> dossierChemin = new ArrayList<Dossier>();

        Dossier parent = ptm.getRoot();
        dossierChemin.add(parent);
        for (i = fsPath.size() - 1; i >= 0; i--) {
            if (importDossier && i == 0) {
                parent.getSousDossiers().remove(fsPath.get(i));
            }
            boolean ret = parent.getSousDossiers().add(fsPath.get(i));
            if (!ret) {
                java.util.Iterator<Dossier> it = parent.getSousDossiers()
                    .iterator();
                while (it.hasNext()) {
                    Dossier next = it.next();
                    if (next.equals(fsPath.get(i))) {
                        parent = next;
                        break;
                    }
                }
            } else {
                parent = fsPath.get(i);
            }

            dossierChemin.add(parent);
        }

        Object[] tabObject = dossierChemin.toArray();

        return new TreePath(tabObject);
    }

    public TreePath findPath(Dossier d) {
        TreePath tp = new TreePath(ptm.getRoot());
        TreePath tp2 = null;
        
        while (!((Dossier)tp.getLastPathComponent()).equals(d)) {
            Dossier act = (Dossier) tp.getLastPathComponent();
            for (Dossier ssDo : act.getSousDossiers()) {
                if (d.getJavaFile().getPath().startsWith(
            ssDo.getJavaFile().getPath())) {
                    tp2 = tp.pathByAddingChild(ssDo);
                } 
            }
            
            if (tp2 != null && tp2.equals(tp)) {
                return null;
            } else if (tp2 != null) {
                tp = tp2;
            }
        }
        
        return tp;
    }
    
    public TreePath delAndReturnParentPath(Dossier d) {
        TreePath tp = findPath(d);
        ptm.removeNodeFromParent((Dossier) tp.getParentPath().getLastPathComponent(), d);
        return tp.getParentPath();
    }
    
    /**
     * Redéfinit la méthode <code>notifyObservers</code> de
     * la classe <code>Observable</code>.
     * <p>
     * Ajoute juste un appel à setChanged avant d'appeler la
     * méthode de la super-classe, sinon les changements ne sont
     * pas pris en compte.
     *
     * @param b - objet à passer à l'observateur
     * @see java.util.Observable#notifyObservers(java.lang.Object)
     */
    @Override
    public void notifyObservers(final Object b) {
        setChanged();
        super.notifyObservers(b);
    }
}
